/*
 * Copyright 2001-2015, Haiku, Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		DarkWyrm <bpmagic@columbus.rr.com>
 *		Caz <turok2@currantbun.com>
 *		Axel Dörfler, axeld@pinc-software.de
 *		Michael Lotz <mmlr@mlotz.ch>
 *		Wim van der Meer <WPJvanderMeer@gmail.com>
 *		Joseph Groover <looncraz@looncraz.net>
 */


/*!	Global functions and variables for the Interface Kit */


#include <InterfaceDefs.h>

#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <Bitmap.h>
#include <Clipboard.h>
#include <ControlLook.h>
#include <Font.h>
#include <Menu.h>
#include <Point.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollBar.h>
#include <String.h>
#include <TextView.h>
#include <Window.h>

#include <ApplicationPrivate.h>
#include <AppServerLink.h>
#include <ColorConversion.h>
#include <DecorInfo.h>
#include <DefaultColors.h>
#include <DesktopLink.h>
#include <HaikuControlLook.h>
#include <InputServerTypes.h>
#include <input_globals.h>
#include <InterfacePrivate.h>
#include <MenuPrivate.h>
#include <pr_server.h>
#include <ServerProtocol.h>
#include <ServerReadOnlyMemory.h>
#include <truncate_string.h>
#include <utf8_functions.h>
#include <WidthBuffer.h>
#include <WindowInfo.h>


using namespace BPrivate;

// some other weird struct exported by BeOS, it's not initialized, though
struct general_ui_info {
	rgb_color	background_color;
	rgb_color	mark_color;
	rgb_color	highlight_color;
	bool		color_frame;
	rgb_color	window_frame_color;
};

struct general_ui_info general_info;

menu_info *_menu_info_ptr_;

extern "C" const char B_NOTIFICATION_SENDER[] = "be:sender";

static const rgb_color _kDefaultColors[kColorWhichCount] = {
	{216, 216, 216, 255},	// B_PANEL_BACKGROUND_COLOR
	{216, 216, 216, 255},	// B_MENU_BACKGROUND_COLOR
	{255, 203, 0, 255},		// B_WINDOW_TAB_COLOR
	{0, 0, 229, 255},		// B_KEYBOARD_NAVIGATION_COLOR
	{51, 102, 152, 255},	// B_DESKTOP_COLOR
	{153, 153, 153, 255},	// B_MENU_SELECTED_BACKGROUND_COLOR
	{0, 0, 0, 255},			// B_MENU_ITEM_TEXT_COLOR
	{0, 0, 0, 255},			// B_MENU_SELECTED_ITEM_TEXT_COLOR
	{0, 0, 0, 255},			// B_MENU_SELECTED_BORDER_COLOR
	{0, 0, 0, 255},			// B_PANEL_TEXT_COLOR
	{255, 255, 255, 255},	// B_DOCUMENT_BACKGROUND_COLOR
	{0, 0, 0, 255},			// B_DOCUMENT_TEXT_COLOR
	{245, 245, 245, 255},	// B_CONTROL_BACKGROUND_COLOR
	{0, 0, 0, 255},			// B_CONTROL_TEXT_COLOR
	{172, 172, 172, 255},	// B_CONTROL_BORDER_COLOR
	{102, 152, 203, 255},	// B_CONTROL_HIGHLIGHT_COLOR
	{0, 0, 0, 255},			// B_NAVIGATION_PULSE_COLOR
	{255, 255, 255, 255},	// B_SHINE_COLOR
	{0, 0, 0, 255},			// B_SHADOW_COLOR
	{255, 255, 216, 255},	// B_TOOLTIP_BACKGROUND_COLOR
	{0, 0, 0, 255},			// B_TOOLTIP_TEXT_COLOR
	{0, 0, 0, 255},			// B_WINDOW_TEXT_COLOR
	{232, 232, 232, 255},	// B_WINDOW_INACTIVE_TAB_COLOR
	{80, 80, 80, 255},		// B_WINDOW_INACTIVE_TEXT_COLOR
	{224, 224, 224, 255},	// B_WINDOW_BORDER_COLOR
	{232, 232, 232, 255},	// B_WINDOW_INACTIVE_BORDER_COLOR
	{27, 82, 140, 255},     // B_CONTROL_MARK_COLOR
	{255, 255, 255, 255},	// B_LIST_BACKGROUND_COLOR
	{190, 190, 190, 255},	// B_LIST_SELECTED_BACKGROUND_COLOR
	{0, 0, 0, 255},			// B_LIST_ITEM_TEXT_COLOR
	{0, 0, 0, 255},			// B_LIST_SELECTED_ITEM_TEXT_COLOR
	{216, 216, 216, 255},	// B_SCROLL_BAR_THUMB_COLOR
	{51, 102, 187, 255},	// B_LINK_TEXT_COLOR
	{102, 152, 203, 255},	// B_LINK_HOVER_COLOR
	{145, 112, 155, 255},	// B_LINK_VISITED_COLOR
	{121, 142, 203, 255},	// B_LINK_ACTIVE_COLOR
	{50, 150, 255, 255},	// B_STATUS_BAR_COLOR
	// 100...
	{46, 204, 64, 255},		// B_SUCCESS_COLOR
	{255, 65, 54, 255},		// B_FAILURE_COLOR
	{}
};
const rgb_color* BPrivate::kDefaultColors = &_kDefaultColors[0];


static const rgb_color _kDefaultColorsDark[kColorWhichCount] = {
	{43, 43, 43, 255},		// B_PANEL_BACKGROUND_COLOR
	{28, 28, 28, 255},		// B_MENU_BACKGROUND_COLOR
	{227, 73, 17, 255},		// B_WINDOW_TAB_COLOR
	{0, 0, 229, 255},		// B_KEYBOARD_NAVIGATION_COLOR
	{51, 102, 152, 255},	// B_DESKTOP_COLOR
	{90, 90, 90, 255},		// B_MENU_SELECTED_BACKGROUND_COLOR
	{255, 255, 255, 255},	// B_MENU_ITEM_TEXT_COLOR
	{255, 255, 255, 255},	// B_MENU_SELECTED_ITEM_TEXT_COLOR
	{0, 0, 0, 255},			// B_MENU_SELECTED_BORDER_COLOR
	{253, 253, 253, 255},	// B_PANEL_TEXT_COLOR
	{0, 0, 0, 255},			// B_DOCUMENT_BACKGROUND_COLOR
	{234, 234, 234, 255},	// B_DOCUMENT_TEXT_COLOR
	{29, 29, 29, 255},		// B_CONTROL_BACKGROUND_COLOR
	{230, 230, 230, 255},	// B_CONTROL_TEXT_COLOR
	{195, 195, 195, 255},	// B_CONTROL_BORDER_COLOR
	{75, 124, 168, 255},	// B_CONTROL_HIGHLIGHT_COLOR
	{0, 0, 0, 255},			// B_NAVIGATION_PULSE_COLOR
	{255, 255, 255, 255},	// B_SHINE_COLOR
	{0, 0, 0, 255},			// B_SHADOW_COLOR
	{76, 68, 79, 255},		// B_TOOLTIP_BACKGROUND_COLOR
	{255, 255, 255, 255},	// B_TOOLTIP_TEXT_COLOR
	{255, 255, 255, 255},	// B_WINDOW_TEXT_COLOR
	{203, 32, 9, 255},		// B_WINDOW_INACTIVE_TAB_COLOR
	{255, 255, 255, 255},	// B_WINDOW_INACTIVE_TEXT_COLOR
	{227, 73, 17, 255},		// B_WINDOW_BORDER_COLOR
	{203, 32, 9, 255},		// B_WINDOW_INACTIVE_BORDER_COLOR
	{27, 82, 140, 255},     // B_CONTROL_MARK_COLOR
	{0, 0, 0, 255},			// B_LIST_BACKGROUND_COLOR
	{90, 90, 90, 255},		// B_LIST_SELECTED_BACKGROUND_COLOR
	{255, 255, 255, 255},	// B_LIST_ITEM_TEXT_COLOR
	{255, 255, 255, 255},	// B_LIST_SELECTED_ITEM_TEXT_COLOR
	{39, 39, 39, 255},		// B_SCROLL_BAR_THUMB_COLOR
	{106, 112, 212, 255},	// B_LINK_TEXT_COLOR
	{102, 152, 203, 255},	// B_LINK_HOVER_COLOR
	{145, 112, 155, 255},	// B_LINK_VISITED_COLOR
	{121, 142, 203, 255},	// B_LINK_ACTIVE_COLOR
	{50, 150, 255, 255},	// B_STATUS_BAR_COLOR
	// 100...
	{46, 204, 64, 255},		// B_SUCCESS_COLOR
	{255, 40, 54, 255},		// B_FAILURE_COLOR
	{}
};


static const char* kColorNames[kColorWhichCount] = {
	"B_PANEL_BACKGROUND_COLOR",
	"B_MENU_BACKGROUND_COLOR",
	"B_WINDOW_TAB_COLOR",
	"B_KEYBOARD_NAVIGATION_COLOR",
	"B_DESKTOP_COLOR",
	"B_MENU_SELECTED_BACKGROUND_COLOR",
	"B_MENU_ITEM_TEXT_COLOR",
	"B_MENU_SELECTED_ITEM_TEXT_COLOR",
	"B_MENU_SELECTED_BORDER_COLOR",
	"B_PANEL_TEXT_COLOR",
	"B_DOCUMENT_BACKGROUND_COLOR",
	"B_DOCUMENT_TEXT_COLOR",
	"B_CONTROL_BACKGROUND_COLOR",
	"B_CONTROL_TEXT_COLOR",
	"B_CONTROL_BORDER_COLOR",
	"B_CONTROL_HIGHLIGHT_COLOR",
	"B_NAVIGATION_PULSE_COLOR",
	"B_SHINE_COLOR",
	"B_SHADOW_COLOR",
	"B_TOOLTIP_BACKGROUND_COLOR",
	"B_TOOLTIP_TEXT_COLOR",
	"B_WINDOW_TEXT_COLOR",
	"B_WINDOW_INACTIVE_TAB_COLOR",
	"B_WINDOW_INACTIVE_TEXT_COLOR",
	"B_WINDOW_BORDER_COLOR",
	"B_WINDOW_INACTIVE_BORDER_COLOR",
	"B_CONTROL_MARK_COLOR",
	"B_LIST_BACKGROUND_COLOR",
	"B_LIST_SELECTED_BACKGROUND_COLOR",
	"B_LIST_ITEM_TEXT_COLOR",
	"B_LIST_SELECTED_ITEM_TEXT_COLOR",
	"B_SCROLL_BAR_THUMB_COLOR",
	"B_LINK_TEXT_COLOR",
	"B_LINK_HOVER_COLOR",
	"B_LINK_VISITED_COLOR",
	"B_LINK_ACTIVE_COLOR",
	"B_STATUS_BAR_COLOR",
	// 100...
	"B_SUCCESS_COLOR",
	"B_FAILURE_COLOR",
	NULL
};

static image_id sControlLookAddon = -1;


namespace BPrivate {


/*!	Fills the \a width, \a height, and \a colorSpace parameters according
	to the window screen's mode.
	Returns \c true if the mode is known.
*/
bool
get_mode_parameter(uint32 mode, int32& width, int32& height,
	uint32& colorSpace)
{
	switch (mode) {
		case B_8_BIT_640x480:
		case B_8_BIT_800x600:
		case B_8_BIT_1024x768:
		case B_8_BIT_1152x900:
		case B_8_BIT_1280x1024:
		case B_8_BIT_1600x1200:
			colorSpace = B_CMAP8;
			break;

		case B_15_BIT_640x480:
		case B_15_BIT_800x600:
		case B_15_BIT_1024x768:
		case B_15_BIT_1152x900:
		case B_15_BIT_1280x1024:
		case B_15_BIT_1600x1200:
			colorSpace = B_RGB15;
			break;

		case B_16_BIT_640x480:
		case B_16_BIT_800x600:
		case B_16_BIT_1024x768:
		case B_16_BIT_1152x900:
		case B_16_BIT_1280x1024:
		case B_16_BIT_1600x1200:
			colorSpace = B_RGB16;
			break;

		case B_32_BIT_640x480:
		case B_32_BIT_800x600:
		case B_32_BIT_1024x768:
		case B_32_BIT_1152x900:
		case B_32_BIT_1280x1024:
		case B_32_BIT_1600x1200:
			colorSpace = B_RGB32;
			break;

		default:
			return false;
	}

	switch (mode) {
		case B_8_BIT_640x480:
		case B_15_BIT_640x480:
		case B_16_BIT_640x480:
		case B_32_BIT_640x480:
			width = 640; height = 480;
			break;

		case B_8_BIT_800x600:
		case B_15_BIT_800x600:
		case B_16_BIT_800x600:
		case B_32_BIT_800x600:
			width = 800; height = 600;
			break;

		case B_8_BIT_1024x768:
		case B_15_BIT_1024x768:
		case B_16_BIT_1024x768:
		case B_32_BIT_1024x768:
			width = 1024; height = 768;
			break;

		case B_8_BIT_1152x900:
		case B_15_BIT_1152x900:
		case B_16_BIT_1152x900:
		case B_32_BIT_1152x900:
			width = 1152; height = 900;
			break;

		case B_8_BIT_1280x1024:
		case B_15_BIT_1280x1024:
		case B_16_BIT_1280x1024:
		case B_32_BIT_1280x1024:
			width = 1280; height = 1024;
			break;

		case B_8_BIT_1600x1200:
		case B_15_BIT_1600x1200:
		case B_16_BIT_1600x1200:
		case B_32_BIT_1600x1200:
			width = 1600; height = 1200;
			break;
	}

	return true;
}


void
get_workspaces_layout(uint32* _columns, uint32* _rows)
{
	int32 columns = 1;
	int32 rows = 1;

	BPrivate::AppServerLink link;
	link.StartMessage(AS_GET_WORKSPACE_LAYOUT);

	status_t status;
	if (link.FlushWithReply(status) == B_OK && status == B_OK) {
		link.Read<int32>(&columns);
		link.Read<int32>(&rows);
	}

	if (_columns != NULL)
		*_columns = columns;
	if (_rows != NULL)
		*_rows = rows;
}


void
set_workspaces_layout(uint32 columns, uint32 rows)
{
	if (columns < 1 || rows < 1)
		return;

	BPrivate::AppServerLink link;
	link.StartMessage(AS_SET_WORKSPACE_LAYOUT);
	link.Attach<int32>(columns);
	link.Attach<int32>(rows);
	link.Flush();
}


}	// namespace BPrivate


void
set_subpixel_antialiasing(bool subpix)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_SET_SUBPIXEL_ANTIALIASING);
	link.Attach<bool>(subpix);
	link.Flush();
}


status_t
get_subpixel_antialiasing(bool* subpix)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_GET_SUBPIXEL_ANTIALIASING);
	int32 status = B_ERROR;
	if (link.FlushWithReply(status) != B_OK || status < B_OK)
		return status;
	link.Read<bool>(subpix);
	return B_OK;
}


void
set_hinting_mode(uint8 hinting)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_SET_HINTING);
	link.Attach<uint8>(hinting);
	link.Flush();
}


status_t
get_hinting_mode(uint8* hinting)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_GET_HINTING);
	int32 status = B_ERROR;
	if (link.FlushWithReply(status) != B_OK || status < B_OK)
		return status;
	link.Read<uint8>(hinting);
	return B_OK;
}


void
set_average_weight(uint8 averageWeight)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_SET_SUBPIXEL_AVERAGE_WEIGHT);
	link.Attach<uint8>(averageWeight);
	link.Flush();
}


status_t
get_average_weight(uint8* averageWeight)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_GET_SUBPIXEL_AVERAGE_WEIGHT);
	int32 status = B_ERROR;
	if (link.FlushWithReply(status) != B_OK || status < B_OK)
		return status;
	link.Read<uint8>(averageWeight);
	return B_OK;
}


void
set_is_subpixel_ordering_regular(bool subpixelOrdering)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_SET_SUBPIXEL_ORDERING);
	link.Attach<bool>(subpixelOrdering);
	link.Flush();
}


status_t
get_is_subpixel_ordering_regular(bool* subpixelOrdering)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_GET_SUBPIXEL_ORDERING);
	int32 status = B_ERROR;
	if (link.FlushWithReply(status) != B_OK || status < B_OK)
		return status;
	link.Read<bool>(subpixelOrdering);
	return B_OK;
}


const color_map *
system_colors()
{
	return BScreen(B_MAIN_SCREEN_ID).ColorMap();
}


status_t
set_screen_space(int32 index, uint32 space, bool stick)
{
	int32 width;
	int32 height;
	uint32 depth;
	if (!BPrivate::get_mode_parameter(space, width, height, depth))
		return B_BAD_VALUE;

	BScreen screen(B_MAIN_SCREEN_ID);
	display_mode mode;

	// TODO: What about refresh rate ?
	// currently we get it from the current video mode, but
	// this might be not so wise.
	status_t status = screen.GetMode(index, &mode);
	if (status < B_OK)
		return status;

	mode.virtual_width = width;
	mode.virtual_height = height;
	mode.space = depth;

	return screen.SetMode(index, &mode, stick);
}


status_t
get_scroll_bar_info(scroll_bar_info *info)
{
	if (info == NULL)
		return B_BAD_VALUE;

	BPrivate::AppServerLink link;
	link.StartMessage(AS_GET_SCROLLBAR_INFO);

	int32 code;
	if (link.FlushWithReply(code) == B_OK
		&& code == B_OK) {
		link.Read<scroll_bar_info>(info);
		return B_OK;
	}

	return B_ERROR;
}


status_t
set_scroll_bar_info(scroll_bar_info *info)
{
	if (info == NULL)
		return B_BAD_VALUE;

	BPrivate::AppServerLink link;
	int32 code;

	link.StartMessage(AS_SET_SCROLLBAR_INFO);
	link.Attach<scroll_bar_info>(*info);

	if (link.FlushWithReply(code) == B_OK
		&& code == B_OK)
		return B_OK;

	return B_ERROR;
}


status_t
get_mouse_type(int32 *type)
{
	BMessage command(IS_GET_MOUSE_TYPE);
	BMessage reply;

	status_t err = _control_input_server_(&command, &reply);
	if (err != B_OK)
		return err;
	return reply.FindInt32("mouse_type", type);
}


status_t
set_mouse_type(int32 type)
{
	BMessage command(IS_SET_MOUSE_TYPE);
	BMessage reply;

	status_t err = command.AddInt32("mouse_type", type);
	if (err != B_OK)
		return err;
	return _control_input_server_(&command, &reply);
}


status_t
get_mouse_type(const char* mouse_name, int32 *type)
{
	BMessage command(IS_GET_MOUSE_TYPE);
	BMessage reply;
	command.AddString("mouse_name", mouse_name);

	status_t err = _control_input_server_(&command, &reply);
	if (err != B_OK)
		return err;

	return reply.FindInt32("mouse_type", type);
}


status_t
set_mouse_type(const char* mouse_name, int32 type)
{
	BMessage command(IS_SET_MOUSE_TYPE);
	BMessage reply;

	status_t err_mouse_name = command.AddString("mouse_name", mouse_name);
	if (err_mouse_name != B_OK)
		return err_mouse_name;

	status_t err = command.AddInt32("mouse_type", type);
	if (err != B_OK)
		return err;
	return _control_input_server_(&command, &reply);
}


status_t
get_mouse_map(mouse_map* map)
{
	return get_mouse_map("", map);
}


status_t
set_mouse_map(mouse_map* map)
{
	return set_mouse_map("", map);
}


status_t
get_mouse_map(const char* mouse_name, mouse_map* map)
{
	BMessage command(IS_GET_MOUSE_MAP);
	BMessage reply;
	const void *data = 0;
	ssize_t count;

	status_t err = command.AddString("mouse_name", mouse_name);
	if (err == B_OK)
		err = _control_input_server_(&command, &reply);
	if (err == B_OK)
		err = reply.FindData("mousemap", B_RAW_TYPE, &data, &count);
	if (err == B_OK)
		memcpy(map, data, count);

	return err;
}


status_t
set_mouse_map(const char* mouse_name, mouse_map* map)
{
	BMessage command(IS_SET_MOUSE_MAP);
	BMessage reply;

	status_t err = command.AddString("mouse_name", mouse_name);
	if (err == B_OK)
		err = command.AddData("mousemap", B_RAW_TYPE, map, sizeof(mouse_map));
	if (err != B_OK)
		return err;
	return _control_input_server_(&command, &reply);
}


status_t
get_click_speed(bigtime_t* speed)
{
	return get_click_speed("", speed);
}


status_t
set_click_speed(bigtime_t speed)
{
	return set_click_speed("", speed);
}


status_t
get_click_speed(const char* mouse_name, bigtime_t* speed)
{
	BMessage command(IS_GET_CLICK_SPEED);
	BMessage reply;

	status_t err = command.AddString("mouse_name", mouse_name);
	if (err == B_OK)
		err = _control_input_server_(&command, &reply);
	if (err != B_OK)
		return err;

	if (reply.FindInt64("speed", speed) != B_OK)
		*speed = 500000;

	return B_OK;
}


status_t
set_click_speed(const char* mouse_name, bigtime_t speed)
{
	BMessage command(IS_SET_CLICK_SPEED);
	BMessage reply;

	status_t err = command.AddString("mouse_name", mouse_name);
	if (err == B_OK)
		err = command.AddInt64("speed", speed);
	if (err != B_OK)
		return err;
	return _control_input_server_(&command, &reply);
}


status_t
get_mouse_speed(int32 *speed)
{
	BMessage command(IS_GET_MOUSE_SPEED);
	BMessage reply;

	status_t err = _control_input_server_(&command, &reply);
	if (err != B_OK)
		return err;

	if (reply.FindInt32("speed", speed) != B_OK)
		*speed = 65536;

	return B_OK;
}


status_t
set_mouse_speed(int32 speed)
{
	BMessage command(IS_SET_MOUSE_SPEED);
	BMessage reply;
	command.AddInt32("speed", speed);
	return _control_input_server_(&command, &reply);
}


status_t
get_mouse_speed(const char* mouse_name, int32 *speed)
{
	BMessage command(IS_GET_MOUSE_SPEED);
	BMessage reply;
	command.AddString("mouse_name", mouse_name);

	status_t err = _control_input_server_(&command, &reply);
	if (err != B_OK)
		return err;

	err = reply.FindInt32("speed", speed);
	if (err != B_OK)
		return err;

	return B_OK;
}


status_t
set_mouse_speed(const char* mouse_name, int32 speed)
{
	BMessage command(IS_SET_MOUSE_SPEED);
	BMessage reply;
	command.AddString("mouse_name", mouse_name);

	command.AddInt32("speed", speed);

	return _control_input_server_(&command, &reply);
}


status_t
get_mouse_acceleration(int32 *speed)
{
	BMessage command(IS_GET_MOUSE_ACCELERATION);
	BMessage reply;

	_control_input_server_(&command, &reply);

	if (reply.FindInt32("speed", speed) != B_OK)
		*speed = 65536;

	return B_OK;
}


status_t
set_mouse_acceleration(int32 speed)
{
	BMessage command(IS_SET_MOUSE_ACCELERATION);
	BMessage reply;
	command.AddInt32("speed", speed);
	return _control_input_server_(&command, &reply);
}


status_t
get_mouse_acceleration(const char* mouse_name, int32 *speed)
{
	BMessage command(IS_GET_MOUSE_ACCELERATION);
	BMessage reply;
	command.AddString("mouse_name", mouse_name);

	_control_input_server_(&command, &reply);

	if (reply.FindInt32("speed", speed) != B_OK)
		*speed = 65536;

	return B_OK;
}


status_t
set_mouse_acceleration(const char* mouse_name, int32 speed)
{
	BMessage command(IS_SET_MOUSE_ACCELERATION);
	BMessage reply;
	command.AddString("mouse_name", mouse_name);

	command.AddInt32("speed", speed);

	return _control_input_server_(&command, &reply);
}


status_t
get_key_repeat_rate(int32 *rate)
{
	BMessage command(IS_GET_KEY_REPEAT_RATE);
	BMessage reply;

	status_t err = _control_input_server_(&command, &reply);

	if (err == B_OK)
		err = reply.FindInt32("rate", rate);

	if (err != B_OK) {
		*rate = 250000;
		return err;
	}

	return B_OK;
}


status_t
set_key_repeat_rate(int32 rate)
{
	BMessage command(IS_SET_KEY_REPEAT_RATE);
	BMessage reply;
	command.AddInt32("rate", rate);
	return _control_input_server_(&command, &reply);
}


status_t
get_key_repeat_delay(bigtime_t *delay)
{
	BMessage command(IS_GET_KEY_REPEAT_DELAY);
	BMessage reply;

	status_t err = _control_input_server_(&command, &reply);

	if (err == B_OK)
		err = reply.FindInt64("delay", delay);

	if (err != B_OK) {
		*delay = 200;
		return err;
	}

	return B_OK;
}


status_t
set_key_repeat_delay(bigtime_t  delay)
{
	BMessage command(IS_SET_KEY_REPEAT_DELAY);
	BMessage reply;
	command.AddInt64("delay", delay);
	return _control_input_server_(&command, &reply);
}


uint32
modifiers()
{
	BMessage command(IS_GET_MODIFIERS);
	BMessage reply;
	int32 err, modifier;

	_control_input_server_(&command, &reply);

	if (reply.FindInt32("status", &err) != B_OK)
		return 0;

	if (reply.FindInt32("modifiers", &modifier) != B_OK)
		return 0;

	return modifier;
}


status_t
get_key_info(key_info *info)
{
	BMessage command(IS_GET_KEY_INFO);
	BMessage reply;
	const void *data = 0;
	int32 err;
	ssize_t count;

	_control_input_server_(&command, &reply);

	if (reply.FindInt32("status", &err) != B_OK)
		return B_ERROR;

	if (reply.FindData("key_info", B_ANY_TYPE, &data, &count) != B_OK)
		return B_ERROR;

	memcpy(info, data, count);
	return B_OK;
}


void
get_key_map(key_map **map, char **key_buffer)
{
	_get_key_map(map, key_buffer, NULL);
}


void
_get_key_map(key_map **map, char **key_buffer, ssize_t *key_buffer_size)
{
	BMessage command(IS_GET_KEY_MAP);
	BMessage reply;
	ssize_t map_count, key_count;
	const void *map_array = 0, *key_array = 0;
	if (key_buffer_size == NULL)
		key_buffer_size = &key_count;

	_control_input_server_(&command, &reply);

	if (reply.FindData("keymap", B_ANY_TYPE, &map_array, &map_count) != B_OK) {
		*map = 0; *key_buffer = 0;
		return;
	}

	if (reply.FindData("key_buffer", B_ANY_TYPE, &key_array, key_buffer_size)
			!= B_OK) {
		*map = 0; *key_buffer = 0;
		return;
	}

	*map = (key_map *)malloc(map_count);
	memcpy(*map, map_array, map_count);
	*key_buffer = (char *)malloc(*key_buffer_size);
	memcpy(*key_buffer, key_array, *key_buffer_size);
}


status_t
get_keyboard_id(uint16 *id)
{
	BMessage command(IS_GET_KEYBOARD_ID);
	BMessage reply;
	uint16 kid;

	_control_input_server_(&command, &reply);

	status_t err = reply.FindInt16("id", (int16 *)&kid);
	if (err != B_OK)
		return err;
	*id = kid;

	return B_OK;
}


status_t
get_modifier_key(uint32 modifier, uint32 *key)
{
	BMessage command(IS_GET_MODIFIER_KEY);
	BMessage reply;
	uint32 rkey;

	command.AddInt32("modifier", modifier);
	_control_input_server_(&command, &reply);

	status_t err = reply.FindInt32("key", (int32 *) &rkey);
	if (err != B_OK)
		return err;
	*key = rkey;

	return B_OK;
}


void
set_modifier_key(uint32 modifier, uint32 key)
{
	BMessage command(IS_SET_MODIFIER_KEY);
	BMessage reply;

	command.AddInt32("modifier", modifier);
	command.AddInt32("key", key);
	_control_input_server_(&command, &reply);
}


void
set_keyboard_locks(uint32 modifiers)
{
	BMessage command(IS_SET_KEYBOARD_LOCKS);
	BMessage reply;

	command.AddInt32("locks", modifiers);
	_control_input_server_(&command, &reply);
}


status_t
_restore_key_map_()
{
	BMessage message(IS_RESTORE_KEY_MAP);
	BMessage reply;

	return _control_input_server_(&message, &reply);
}


rgb_color
keyboard_navigation_color()
{
	// Queries the app_server
	return ui_color(B_KEYBOARD_NAVIGATION_COLOR);
}


int32
count_workspaces()
{
	uint32 columns;
	uint32 rows;
	BPrivate::get_workspaces_layout(&columns, &rows);

	return columns * rows;
}


void
set_workspace_count(int32 count)
{
	int32 squareRoot = (int32)sqrt(count);

	int32 rows = 1;
	for (int32 i = 2; i <= squareRoot; i++) {
		if (count % i == 0)
			rows = i;
	}

	int32 columns = count / rows;

	BPrivate::set_workspaces_layout(columns, rows);
}


int32
current_workspace()
{
	int32 index = 0;

	BPrivate::AppServerLink link;
	link.StartMessage(AS_CURRENT_WORKSPACE);

	int32 status;
	if (link.FlushWithReply(status) == B_OK && status == B_OK)
		link.Read<int32>(&index);

	return index;
}


void
activate_workspace(int32 workspace)
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_ACTIVATE_WORKSPACE);
	link.Attach<int32>(workspace);
	link.Attach<bool>(false);
	link.Flush();
}


bigtime_t
idle_time()
{
	bigtime_t idletime = 0;

	BPrivate::AppServerLink link;
	link.StartMessage(AS_IDLE_TIME);

	int32 code;
	if (link.FlushWithReply(code) == B_OK && code == B_OK)
		link.Read<int64>(&idletime);

	return idletime;
}


void
run_select_printer_panel()
{
	if (be_roster == NULL)
		return;

	// Launches the Printer prefs app via the Roster
	be_roster->Launch(PRNT_SIGNATURE_TYPE);
}


void
run_add_printer_panel()
{
	// Launches the Printer prefs app via the Roster and asks it to
	// add a printer
	run_select_printer_panel();

	BMessenger printerPanelMessenger(PRNT_SIGNATURE_TYPE);
	printerPanelMessenger.SendMessage(PRINTERS_ADD_PRINTER);
}


void
run_be_about()
{
	if (be_roster != NULL)
		be_roster->Launch("application/x-vnd.Haiku-About");
}


void
set_focus_follows_mouse(bool follow)
{
	// obviously deprecated API
	set_mouse_mode(follow ? B_FOCUS_FOLLOWS_MOUSE : B_NORMAL_MOUSE);
}


bool
focus_follows_mouse()
{
	return mouse_mode() == B_FOCUS_FOLLOWS_MOUSE;
}


void
set_mouse_mode(mode_mouse mode)
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_SET_MOUSE_MODE);
	link.Attach<mode_mouse>(mode);
	link.Flush();
}


mode_mouse
mouse_mode()
{
	// Gets the mouse focus style, such as activate to click,
	// focus to click, ...
	mode_mouse mode = B_NORMAL_MOUSE;

	BPrivate::AppServerLink link;
	link.StartMessage(AS_GET_MOUSE_MODE);

	int32 code;
	if (link.FlushWithReply(code) == B_OK && code == B_OK)
		link.Read<mode_mouse>(&mode);

	return mode;
}


void
set_focus_follows_mouse_mode(mode_focus_follows_mouse mode)
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_SET_FOCUS_FOLLOWS_MOUSE_MODE);
	link.Attach<mode_focus_follows_mouse>(mode);
	link.Flush();
}


mode_focus_follows_mouse
focus_follows_mouse_mode()
{
	mode_focus_follows_mouse mode = B_NORMAL_FOCUS_FOLLOWS_MOUSE;

	BPrivate::AppServerLink link;
	link.StartMessage(AS_GET_FOCUS_FOLLOWS_MOUSE_MODE);

	int32 code;
	if (link.FlushWithReply(code) == B_OK && code == B_OK)
		link.Read<mode_focus_follows_mouse>(&mode);

	return mode;
}


status_t
get_mouse(BPoint* screenWhere, uint32* buttons)
{
	if (screenWhere == NULL && buttons == NULL)
		return B_BAD_VALUE;

	BPrivate::AppServerLink link;
	link.StartMessage(AS_GET_CURSOR_POSITION);

	int32 code;
	status_t ret = link.FlushWithReply(code);
	if (ret != B_OK)
		return ret;
	if (code != B_OK)
		return code;

	if (screenWhere != NULL)
		ret = link.Read<BPoint>(screenWhere);
	else {
		BPoint dummy;
		ret = link.Read<BPoint>(&dummy);
	}
	if (ret != B_OK)
		return ret;

	if (buttons != NULL)
		ret = link.Read<uint32>(buttons);
	else {
		uint32 dummy;
		ret = link.Read<uint32>(&dummy);
	}

	return ret;
}


status_t
get_mouse_bitmap(BBitmap** bitmap, BPoint* hotspot)
{
	if (bitmap == NULL && hotspot == NULL)
		return B_BAD_VALUE;

	BPrivate::AppServerLink link;
	link.StartMessage(AS_GET_CURSOR_BITMAP);

	int32 code;
	status_t status = link.FlushWithReply(code);
	if (status != B_OK)
		return status;
	if (code != B_OK)
		return code;

	uint32 size = 0;
	uint32 cursorWidth = 0;
	uint32 cursorHeight = 0;
	color_space colorspace = B_RGBA32;

	// if link.Read() returns an error, the same error will be returned on
	// subsequent calls, so we'll check only the return value of the last call
	link.Read<uint32>(&size);
	link.Read<uint32>(&cursorWidth);
	link.Read<uint32>(&cursorHeight);
	link.Read<color_space>(&colorspace);
	if (hotspot == NULL) {
		BPoint dummy;
		link.Read<BPoint>(&dummy);
	} else
		link.Read<BPoint>(hotspot);

	void* data = NULL;
	if (size > 0)
		data = malloc(size);
	if (data == NULL)
		return B_NO_MEMORY;

	status = link.Read(data, size);
	if (status != B_OK) {
		free(data);
		return status;
	}

	BBitmap* cursorBitmap = new (std::nothrow) BBitmap(BRect(0, 0,
		cursorWidth - 1, cursorHeight - 1), colorspace);

	if (cursorBitmap == NULL) {
		free(data);
		return B_NO_MEMORY;
	}
	status = cursorBitmap->InitCheck();
	if (status == B_OK)
		cursorBitmap->SetBits(data, size, 0, colorspace);

	free(data);

	if (status == B_OK && bitmap != NULL)
		*bitmap = cursorBitmap;
	else
		delete cursorBitmap;

	return status;
}


void
set_accept_first_click(bool acceptFirstClick)
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_SET_ACCEPT_FIRST_CLICK);
	link.Attach<bool>(acceptFirstClick);
	link.Flush();
}


bool
accept_first_click()
{
	// Gets the accept first click status
	bool acceptFirstClick = true;

	BPrivate::AppServerLink link;
	link.StartMessage(AS_GET_ACCEPT_FIRST_CLICK);

	int32 code;
	if (link.FlushWithReply(code) == B_OK && code == B_OK)
		link.Read<bool>(&acceptFirstClick);

	return acceptFirstClick;
}


rgb_color
ui_color(color_which which)
{
	int32 index = color_which_to_index(which);
	if (index < 0 || index >= kColorWhichCount) {
		fprintf(stderr, "ui_color(): unknown color_which %d\n", which);
		return make_color(0, 0, 0);
	}

	if (be_app != NULL) {
		server_read_only_memory* shared
			= BApplication::Private::ServerReadOnlyMemory();
		if (shared != NULL) {
			// check for unset colors
			if (shared->colors[index] == B_TRANSPARENT_COLOR)
				shared->colors[index] = _kDefaultColors[index];

			return shared->colors[index];
		}
	}

	return _kDefaultColors[index];
}


rgb_color
BPrivate::GetSystemColor(color_which colorConstant, bool darkVariant) {
	if (darkVariant) {
		return _kDefaultColorsDark[color_which_to_index(colorConstant)];
	} else {
		return _kDefaultColors[color_which_to_index(colorConstant)];
	}
}


const char*
ui_color_name(color_which which)
{
	// Suppress warnings for B_NO_COLOR.
	if (which == B_NO_COLOR)
		return NULL;

	int32 index = color_which_to_index(which);
	if (index < 0 || index >= kColorWhichCount) {
		fprintf(stderr, "ui_color_name(): unknown color_which %d\n", which);
		return NULL;
	}

	return kColorNames[index];
}


color_which
which_ui_color(const char* name)
{
	if (name == NULL)
		return B_NO_COLOR;

	for (int32 index = 0; index < kColorWhichCount; ++index) {
		if (!strcmp(kColorNames[index], name))
			return index_to_color_which(index);
	}

	return B_NO_COLOR;
}


void
set_ui_color(const color_which &which, const rgb_color &color)
{
	int32 index = color_which_to_index(which);
	if (index < 0 || index >= kColorWhichCount) {
		fprintf(stderr, "set_ui_color(): unknown color_which %d\n", which);
		return;
	}

	if (ui_color(which) == color)
		return;

	BPrivate::DesktopLink link;
	link.StartMessage(AS_SET_UI_COLOR);
	link.Attach<color_which>(which);
	link.Attach<rgb_color>(color);
	link.Flush();
}


void
set_ui_colors(const BMessage* colors)
{
	if (colors == NULL)
		return;

	int32 count = 0;
	int32 index = 0;
	char* name = NULL;
	type_code type;
	rgb_color color;
	color_which which = B_NO_COLOR;

	BPrivate::DesktopLink desktop;
	if (desktop.InitCheck() != B_OK)
		return;

	desktop.StartMessage(AS_SET_UI_COLORS);
	desktop.Attach<bool>(false);

	// Only colors with names that map to system colors will get through.
	while (colors->GetInfo(B_RGB_32_BIT_TYPE, index, &name, &type) == B_OK) {

		which = which_ui_color(name);
		++index;

		if (which == B_NO_COLOR || colors->FindColor(name, &color) != B_OK)
			continue;

		desktop.Attach<color_which>(which);
		desktop.Attach<rgb_color>(color);
		++count;
	}

	if (count == 0)
		return;

	desktop.Attach<color_which>(B_NO_COLOR);
	desktop.Flush();
}


rgb_color
tint_color(rgb_color color, float tint)
{
	rgb_color result;

	#define LIGHTEN(x) ((uint8)(255.0f - (255.0f - x) * tint))
	#define DARKEN(x)  ((uint8)(x * (2 - tint)))

	if (tint < 1.0f) {
		result.red   = LIGHTEN(color.red);
		result.green = LIGHTEN(color.green);
		result.blue  = LIGHTEN(color.blue);
		result.alpha = color.alpha;
	} else {
		result.red   = DARKEN(color.red);
		result.green = DARKEN(color.green);
		result.blue  = DARKEN(color.blue);
		result.alpha = color.alpha;
	}

	#undef LIGHTEN
	#undef DARKEN

	return result;
}


rgb_color shift_color(rgb_color color, float shift);

rgb_color
shift_color(rgb_color color, float shift)
{
	return tint_color(color, shift);
}


extern "C" status_t
_init_interface_kit_()
{
	status_t status = BPrivate::PaletteConverter::InitializeDefault(true);
	if (status < B_OK)
		return status;

	// init global clipboard
	if (be_clipboard == NULL)
		be_clipboard = new BClipboard(NULL);

	BString path;
	if (get_control_look(path) && path.Length() > 0) {
		BControlLook* (*instantiate)(image_id);

		sControlLookAddon = load_add_on(path.String());
		if (sControlLookAddon >= 0
			&& get_image_symbol(sControlLookAddon,
				"instantiate_control_look",
				B_SYMBOL_TYPE_TEXT, (void **)&instantiate) == B_OK) {
			be_control_look = instantiate(sControlLookAddon);
			if (be_control_look == NULL) {
				unload_add_on(sControlLookAddon);
				sControlLookAddon = -1;
			}
		}
	}
	if (be_control_look == NULL)
		be_control_look = new HaikuControlLook();

	_init_global_fonts_();

	BPrivate::gWidthBuffer = new BPrivate::WidthBuffer;
	status = BPrivate::MenuPrivate::CreateBitmaps();
	if (status != B_OK)
		return status;

	_menu_info_ptr_ = &BMenu::sMenuInfo;

	status = get_menu_info(&BMenu::sMenuInfo);
	if (status != B_OK)
		return status;

	general_info.background_color = ui_color(B_PANEL_BACKGROUND_COLOR);
	general_info.mark_color = ui_color(B_CONTROL_MARK_COLOR);
	general_info.highlight_color = ui_color(B_CONTROL_HIGHLIGHT_COLOR);
	general_info.window_frame_color = ui_color(B_WINDOW_TAB_COLOR);
	general_info.color_frame = true;

	// TODO: fill the other static members

	return status;
}


extern "C" status_t
_fini_interface_kit_()
{
	BPrivate::MenuPrivate::DeleteBitmaps();

	delete BPrivate::gWidthBuffer;
	BPrivate::gWidthBuffer = NULL;

	delete be_control_look;
	be_control_look = NULL;

	// Note: if we ever want to support live switching, we cannot just unload
	// the old one since some thread might still be in a method of the object.
	// maybe locking/unlocking all loopers around would ensure proper exit.
	if (sControlLookAddon >= 0)
		unload_add_on(sControlLookAddon);
	sControlLookAddon = -1;

	// TODO: Anything else?

	return B_OK;
}



namespace BPrivate {


/*!	\brief queries the server for the current decorator
	\param path BString into which to store current decorator's location
	\return boolean true/false
*/
bool
get_decorator(BString& path)
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_GET_DECORATOR);

	int32 code;
	if (link.FlushWithReply(code) != B_OK || code != B_OK)
		return false;

	return link.ReadString(path) == B_OK;
}


/*!	\brief Private function which sets the window decorator for the system.
	\param path BString with the path to the decorator to set

	Will return detailed error status via status_t
*/
status_t
set_decorator(const BString& path)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_SET_DECORATOR);

	link.AttachString(path.String());
	link.Flush();

	status_t error = B_OK;
	link.Read<status_t>(&error);

	return error;
}


/*! \brief sets a window to preview a given decorator
	\param path path to any given decorator add-on
	\param window pointer to BWindow which will show decorator

	Piggy-backs on BWindow::SetDecoratorSettings(...)
*/
status_t
preview_decorator(const BString& path, BWindow* window)
{
	if (window == NULL)
		return B_ERROR;

	BMessage msg('prVu');
	msg.AddString("preview", path.String());

	return window->SetDecoratorSettings(msg);
}


/*!	\brief queries the server for the current ControlLook path
	\param path BString into which to store current ControlLook's add-on path
	\return boolean true/false
*/
bool
get_control_look(BString& path)
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_GET_CONTROL_LOOK);

	int32 code;
	if (link.FlushWithReply(code) != B_OK || code != B_OK)
		return false;

	return link.ReadString(path) == B_OK;
}


/*!	\brief Private function which sets the ControlLook for the system.
	\param BString with the ControlLook add-on path to set

	Will return detailed error status via status_t
*/
status_t
set_control_look(const BString& path)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_SET_CONTROL_LOOK);

	link.AttachString(path.String());

	status_t error = B_OK;
	if (link.FlushWithReply(error) != B_OK)
		return B_ERROR;

	return error;
}


status_t
get_application_order(int32 workspace, team_id** _applications,
	int32* _count)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_GET_APPLICATION_ORDER);
	link.Attach<int32>(workspace);

	int32 code;
	status_t status = link.FlushWithReply(code);
	if (status != B_OK)
		return status;
	if (code != B_OK)
		return code;

	int32 count;
	link.Read<int32>(&count);

	*_applications = (team_id*)malloc(count * sizeof(team_id));
	if (*_applications == NULL)
		return B_NO_MEMORY;

	link.Read(*_applications, count * sizeof(team_id));
	*_count = count;
	return B_OK;
}


status_t
get_window_order(int32 workspace, int32** _tokens, int32* _count)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_GET_WINDOW_ORDER);
	link.Attach<int32>(workspace);

	int32 code;
	status_t status = link.FlushWithReply(code);
	if (status != B_OK)
		return status;
	if (code != B_OK)
		return code;

	int32 count;
	link.Read<int32>(&count);

	*_tokens = (int32*)malloc(count * sizeof(int32));
	if (*_tokens == NULL)
		return B_NO_MEMORY;

	link.Read(*_tokens, count * sizeof(int32));
	*_count = count;
	return B_OK;
}


}	// namespace BPrivate

// These methods were marked with "Danger, will Robinson!" in
// the OpenTracker source, so we might not want to be compatible
// here.
// In any way, we would need to update Deskbar to use our
// replacements, so we could as well just implement them...

void
do_window_action(int32 windowToken, int32 action, BRect zoomRect, bool zoom)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_WINDOW_ACTION);
	link.Attach<int32>(windowToken);
	link.Attach<int32>(action);
		// we don't have any zooming effect

	link.Flush();
}


client_window_info*
get_window_info(int32 serverToken)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_GET_WINDOW_INFO);
	link.Attach<int32>(serverToken);

	int32 code;
	if (link.FlushWithReply(code) != B_OK || code != B_OK)
		return NULL;

	int32 size;
	link.Read<int32>(&size);

	client_window_info* info = (client_window_info*)malloc(size);
	if (info == NULL)
		return NULL;

	link.Read(info, size);
	return info;
}


int32*
get_token_list(team_id team, int32* _count)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_GET_WINDOW_LIST);
	link.Attach<team_id>(team);

	int32 code;
	if (link.FlushWithReply(code) != B_OK || code != B_OK)
		return NULL;

	int32 count;
	link.Read<int32>(&count);

	int32* tokens = (int32*)malloc(count * sizeof(int32));
	if (tokens == NULL)
		return NULL;

	link.Read(tokens, count * sizeof(int32));
	*_count = count;
	return tokens;
}


void
do_bring_to_front_team(BRect zoomRect, team_id team, bool zoom)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_BRING_TEAM_TO_FRONT);
	link.Attach<team_id>(team);
		// we don't have any zooming effect

	link.Flush();
}


void
do_minimize_team(BRect zoomRect, team_id team, bool zoom)
{
	BPrivate::AppServerLink link;

	link.StartMessage(AS_MINIMIZE_TEAM);
	link.Attach<team_id>(team);
		// we don't have any zooming effect

	link.Flush();
}


//	#pragma mark - truncate string


void
truncate_string(BString& string, uint32 mode, float width,
	const float* escapementArray, float fontSize, float ellipsisWidth,
	int32 charCount)
{
	// add a tiny amount to the width to make floating point inaccuracy
	// not drop chars that would actually fit exactly
	width += 1.f / 128;

	switch (mode) {
		case B_TRUNCATE_BEGINNING:
		{
			float totalWidth = 0;
			for (int32 i = charCount - 1; i >= 0; i--) {
				float charWidth = escapementArray[i] * fontSize;
				if (totalWidth + charWidth > width) {
					// we need to truncate
					while (totalWidth + ellipsisWidth > width) {
						// remove chars until there's enough space for the
						// ellipsis
						if (++i == charCount) {
							// we've reached the end of the string and still
							// no space, so return an empty string
							string.Truncate(0);
							return;
						}

						totalWidth -= escapementArray[i] * fontSize;
					}

					string.RemoveChars(0, i + 1);
					string.PrependChars(B_UTF8_ELLIPSIS, 1);
					return;
				}

				totalWidth += charWidth;
			}

			break;
		}

		case B_TRUNCATE_END:
		{
			float totalWidth = 0;
			for (int32 i = 0; i < charCount; i++) {
				float charWidth = escapementArray[i] * fontSize;
				if (totalWidth + charWidth > width) {
					// we need to truncate
					while (totalWidth + ellipsisWidth > width) {
						// remove chars until there's enough space for the
						// ellipsis
						if (i-- == 0) {
							// we've reached the start of the string and still
							// no space, so return an empty string
							string.Truncate(0);
							return;
						}

						totalWidth -= escapementArray[i] * fontSize;
					}

					string.RemoveChars(i, charCount - i);
					string.AppendChars(B_UTF8_ELLIPSIS, 1);
					return;
				}

				totalWidth += charWidth;
			}

			break;
		}

		case B_TRUNCATE_MIDDLE:
		case B_TRUNCATE_SMART:
		{
			float leftWidth = 0;
			float rightWidth = 0;
			int32 leftIndex = 0;
			int32 rightIndex = charCount - 1;
			bool left = true;

			for (int32 i = 0; i < charCount; i++) {
				float charWidth
					= escapementArray[left ? leftIndex : rightIndex] * fontSize;

				if (leftWidth + rightWidth + charWidth > width) {
					// we need to truncate
					while (leftWidth + rightWidth + ellipsisWidth > width) {
						// remove chars until there's enough space for the
						// ellipsis
						if (leftIndex == 0 && rightIndex == charCount - 1) {
							// we've reached both ends of the string and still
							// no space, so return an empty string
							string.Truncate(0);
							return;
						}

						if (leftIndex > 0 && (rightIndex == charCount - 1
								|| leftWidth > rightWidth)) {
							// remove char on the left
							leftWidth -= escapementArray[--leftIndex]
								* fontSize;
						} else {
							// remove char on the right
							rightWidth -= escapementArray[++rightIndex]
								* fontSize;
						}
					}

					string.RemoveChars(leftIndex, rightIndex + 1 - leftIndex);
					string.InsertChars(B_UTF8_ELLIPSIS, 1, leftIndex);
					return;
				}

				if (left) {
					leftIndex++;
					leftWidth += charWidth;
				} else {
					rightIndex--;
					rightWidth += charWidth;
				}

				left = rightWidth > leftWidth;
			}

			break;
		}
	}

	// we've run through without the need to truncate, leave the string as it is
}
